#pragma once

#include "znx_conn.h"
#include "comm/znx_string.h"
#include "comm/znx_array.h"
#include "comm/znx_pool.h"
#include "comm/znx_crc.h"
#include "comm/znx_rwlock.h"

#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/bn.h>
#include <openssl/conf.h>
#include <openssl/crypto.h>
#include <openssl/dh.h>
#ifndef OPENSSL_NO_ENGINE
#include <openssl/engine.h>
#endif
#include <openssl/evp.h>
#include <openssl/hmac.h>
#ifndef OPENSSL_NO_OCSP
#include <openssl/ocsp.h>
#endif
#include <openssl/rand.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>

#if (defined LIBRESSL_VERSION_NUMBER && OPENSSL_VERSION_NUMBER == 0x20000000L)
#undef OPENSSL_VERSION_NUMBER
#if (LIBRESSL_VERSION_NUMBER >= 0x2080000fL)
#define OPENSSL_VERSION_NUMBER  0x1010000fL
#else
#define OPENSSL_VERSION_NUMBER          0x1000107fL
#endif
#endif

#if (OPENSSL_VERSION_NUMBER >= 0x10100001L)

#define znx_ssl_version()               OpenSSL_version(OPENSSL_VERSION)

#else

#define znx_ssl_version()               SSLeay_version(SSLEAY_VERSION)

#endif

#if (OPENSSL_VERSION_NUMBER < 0x10002000L)
#define SSL_is_server(s)                (s)->server
#endif


#define ZNX_SSL_BUFSIZE                 16384
#define ZNX_SSL_MAX_SESSION_SIZE        4096

#define ZNX_SSL_SSLv2                   0x0002
#define ZNX_SSL_SSLv3                   0x0004
#define ZNX_SSL_TLSv1                   0x0008
#define ZNX_SSL_TLSv1_1                 0x0010
#define ZNX_SSL_TLSv1_2                 0x0020
#define ZNX_SSL_TLSv1_3                 0x0040


typedef struct znx_ssl_conf_s           znx_ssl_conf_t;
typedef struct znx_ssl_conn_s           znx_ssl_conn_t;


struct znx_ssl_conf_s {
    SSL_CTX                     *ctx;
    size_t                      buffer_size;
    znx_bool_t                  async_enable;
    znx_pool_t                  *pool;
    znx_array_t                 *passwords;

    znx_rwlock_t                rwlock;
    znx_array_t                 *ticket_keys;
};


struct znx_ssl_conn_s {
    SSL                         *ssl;
    SSL_CTX                     *ssl_ctx;
    SSL_SESSION                 *session;

    int                         last;

    znx_conn_handle_pt          handler;

#if 0
    znx_conn_handle_pt          save_session;
#endif

    int64_t                     handshake_start_ms;
    int64_t                     handshake_end_ms;

    unsigned                    handshaked:1;
    unsigned                    handshake_rejected:1;
    unsigned                    renegotiation:1;
    unsigned                    buffer:1;
    unsigned                    no_wait_shutdown:1;
    unsigned                    no_send_shutdown:1;
    unsigned                    handshake_buffer_set:1;
    unsigned                    in_ocsp:1;
    unsigned                    write_blocked:1;
};


typedef struct {
    int64_t                     expire_ms;
    uint32_t                    crc;
    u_char                      id[32];
    size_t                      id_len;
    u_char                      *session;
    size_t                      session_len;
} znx_ssl_session_id_t;


typedef struct {
    uint32_t                    size;
    u_char                      name[16];
    u_char                      hmac_key[32];
    u_char                      aes_key[32];
} znx_ssl_session_ticket_key_t;

static inline znx_ssl_session_id_t *
znx_ssl_session_id_create(u_char *id, size_t id_len,
    u_char *session, size_t session_len, int64_t expire_ms)
{
    znx_ssl_session_id_t        *session_id;
    session_id = znx_thread_calloc(sizeof(znx_ssl_session_id_t) + session_len);
    session_id->session = (u_char *)(session_id) + sizeof(znx_ssl_session_id_t);
    znx_memcpy(session_id->id, id, id_len);
    session_id->id_len = id_len;
    session_id->crc = znx_crc32_long(id, id_len);
    znx_memcpy(session_id->session, session, session_len);
    session_id->session_len = session_len;
    session_id->expire_ms = expire_ms;

    return session_id;
}


static inline void
znx_ssl_session_id_destroy(znx_ssl_session_id_t *session_id)
{
    znx_thread_free(session_id);
}


static inline int
znx_ssl_session_id_cmp(void *a, void *b) {
    znx_ssl_session_id_t *session_id_a = a;
    znx_ssl_session_id_t *session_id_b = b;

    if (session_id_a->crc > session_id_b->crc) {
        return 1;
    }

    if (session_id_a->crc < session_id_b->crc) {
        return -1;
    }

    if (session_id_a->id_len > session_id_b->id_len) {
        return 1;
    }

    if (session_id_a->id_len < session_id_b->id_len) {
        return -1;
    }

    return znx_strncmp((char *)session_id_a->id,
        (char *)session_id_b->id, session_id_a->id_len);
}


znx_bool_t znx_ssl_init();
znx_ssl_conf_t *znx_ssl_conf_create(uint64_t protocols,
    void *data, char **err);
void znx_ssl_conf_destroy(znx_ssl_conf_t *ssl_conf);


STACK_OF(X509) *znx_parse_pem_cert(const u_char *pem,
    size_t pem_len, char **err);
void znx_free_cert(STACK_OF(X509) *chain);

EVP_PKEY *znx_parse_pem_priv_key(const u_char *pem_key,
    size_t pem_key_len, znx_array_t *passwords, char **err);
void znx_free_priv_key(EVP_PKEY *pkey);

znx_bool_t znx_ssl_set_cert(SSL *ssl, STACK_OF(X509) *chain, char **err);
znx_bool_t znx_ssl_conn_set_chain(znx_ssl_conn_t *conn,
    STACK_OF(X509) * chain, char **err);
znx_bool_t znx_ssl_set_priv_key(SSL *ssl, EVP_PKEY *pkey, char **err);
znx_bool_t znx_ssl_conn_set_priv_key(znx_ssl_conn_t *conn,
    EVP_PKEY *pkey, char **err);

znx_bool_t znx_ssl_ciphers(znx_ssl_conf_t *ssl_conf, const u_char *ciphers,
    znx_bool_t prefer_server_ciphers, char **err);

znx_bool_t ngx_ssl_client_certificate_set(znx_ssl_conf_t *ssl_conf,
    STACK_OF(X509) *ca_certs, char **err);
znx_bool_t znx_ssl_client_certificate(znx_ssl_conf_t *ssl_conf,
    znx_str_t *cert_buf, int depth, char **err);
znx_bool_t znx_ssl_trusted_certificate(znx_ssl_conf_t *ssl_conf,
    znx_str_t *cert_str, int depth, char **err);

znx_bool_t znx_ssl_crl(znx_ssl_conf_t *ssl_conf, znx_str_t *crl, char **err);

znx_bool_t znx_ssl_dhparam(znx_ssl_conf_t *ssl_conf, znx_str_t *file, char **err);

znx_bool_t znx_ssl_ecdh_curve(znx_ssl_conf_t *ssl_conf, znx_str_t *name, char **err);

// commands elements is znx_keyval_t
znx_bool_t znx_ssl_conf_commands(znx_ssl_conf_t *ssl_conf, znx_array_t *commands, char **err);

#if 0
znx_bool_t znx_ssl_client_session_cache(znx_ssl_conf_t *ssl_conf,
    znx_bool_t enable, char **err);
#endif


void znx_ssl_session_cache_init(int64_t max_items);

void znx_ssl_session_cache_put(znx_ssl_session_id_t *session_id);

SSL_SESSION *znx_ssl_session_cache_get(znx_ssl_session_id_t *target);

void znx_ssl_session_cache_remove(znx_ssl_session_id_t *target);

void znx_ssl_remove_cached_session(SSL_CTX *ssl_ctx, SSL_SESSION *ssl_session);

znx_bool_t znx_ssl_session_cache(znx_ssl_conf_t *ssl_conf, znx_str_t *sess_ctx,
    znx_array_t *certificates, znx_bool_t enable,
    int64_t timeout_ms, char **err);

znx_bool_t znx_ssl_session_ticket_key(znx_ssl_conf_t *ssl_conf,
    znx_str_t *key, char **err);

znx_bool_t znx_ssl_create_connection(znx_ssl_conf_t *ssl_conf, znx_conn_t *c,
    znx_conn_handle_pt handler, znx_bool_t client, char **err);

znx_bool_t znx_ssl_set_session(znx_conn_t *c, SSL_SESSION *ssl_session, char **err);

SSL_SESSION *znx_ssl_get_session(znx_conn_t *c);

SSL_SESSION *znx_ssl_get0_session(znx_conn_t *c);

#define znx_ssl_free_session        SSL_SESSION_free

#define znx_ssl_verify_error_optional(n)                        \
    (n == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT                \
     || n == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN               \
     || n == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY       \
     || n == X509_V_ERR_CERT_UNTRUSTED                          \
     || n == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE)


znx_bool_t znx_ssl_check_host(znx_conn_t *c, znx_str_t *name);

#define znx_ssl_waiting_for_async(c)    SSL_waiting_for_async(c->ssl->ssl)


static inline const char *
znx_ssl_get_protocol(znx_conn_t *c)
{
    return SSL_get_version(c->ssl->ssl);
}


static inline const char *
znx_ssl_get_cipher_name(znx_conn_t *c)
{
    return SSL_get_cipher_name(c->ssl->ssl);
}

znx_bool_t znx_ssl_get_ciphers(znx_conn_t *c, znx_pool_t *pool, znx_str_t *s);

znx_bool_t znx_ssl_get_curves(znx_conn_t *c, znx_pool_t *pool, znx_str_t *s);

static inline znx_bool_t
znx_ssl_session_reused(znx_conn_t *c)
{
    if (SSL_session_reused(c->ssl->ssl)) {
        return ZNX_TRUE;
    }

    return ZNX_FALSE;
}


znx_bool_t znx_ssl_get_server_name(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s);

// in openssl, sni parse behind client_hello_cb.
// therefore we provide a function that resolves at the client_hello_cb.
znx_bool_t znx_ssl_get_client_hello_server_name(znx_conn_t *c,
    znx_str_t *s, char **err);

znx_bool_t znx_ssl_get_raw_certificate(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err);

znx_bool_t znx_ssl_get_subject_dn(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err);

znx_bool_t znx_ssl_get_issuer_dn(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err);

znx_bool_t znx_ssl_get_issuer_dn(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err);

znx_bool_t znx_ssl_get_subject_dn_legacy(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err);

znx_bool_t znx_ssl_get_issuer_dn_legacy(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err);

znx_bool_t znx_ssl_get_serial_number(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err);

znx_bool_t znx_ssl_get_fingerprint(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err);

void znx_ssl_get_client_verify(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s);

znx_bool_t znx_ssl_get_client_v_start(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err);

znx_bool_t znx_ssl_get_client_v_end(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err);

znx_bool_t znx_ssl_get_client_v_remain(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err);

znx_bool_t znx_ssl_ocsp_get_status(znx_conn_t *c, const char **s);

znx_result_t znx_ssl_ocsp_validate(znx_conn_t *c);

void znx_ssl_ocsp_cleanup(znx_conn_t *c);


int64_t
znx_ssl_get_handshake_time_ms(znx_conn_t *c)
{
    if (c->ssl == NULL) {
        return 0;
    }

    int64_t end_ms = c->ssl->handshake_end_ms;
    int64_t start_ms = c->ssl->handshake_start_ms;

    int64_t ret = end_ms - start_ms;
    if (ret < 0) {
        return 0;
    }

    return ret;
}


znx_result_t znx_ssl_handshake(znx_conn_t *c);

znx_syscall_result_t znx_ssl_write(znx_conn_t *c, void *buff, size_t count);

znx_syscall_result_t znx_ssl_read(znx_conn_t *c, void *buff, size_t count);

znx_result_t znx_ssl_shutdown(znx_conn_t *c);


#if 0
znx_bool_t ngx_ssl_stapling(znx_ssl_conf_t *ssl_conf, znx_str_t *file,
    znx_str_t *responder, znx_bool_t verify, char **err);

znx_bool_t ngx_ssl_stapling_resolver(znx_ssl_conf_t *ssl_conf,
    znx_resolver_t *resolver, int64_t resolver_timeout_ms, char **err);

ngx_int_t ngx_ssl_get_escaped_certificate(ngx_connection_t *c, ngx_pool_t *pool,
    ngx_str_t *s);
#endif



#if 0

void ngx_cdecl ngx_ssl_error(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
    char *fmt, ...);
void ngx_ssl_cleanup_ctx(void *data);

#if (NGX_SSL && NGX_SSL_ASYNC)
ngx_int_t ngx_ssl_async_process_fds(ngx_connection_t *c) ;
#endif

#endif



extern int  znx_ssl_conn_index;
extern int  znx_ssl_server_conf_index;
extern int  znx_ssl_conf_index;
extern int  znx_ssl_session_ticket_keys_index;
extern int  znx_ssl_certificate_index;
extern int  znx_ssl_next_certificate_index;
extern int  znx_ssl_certificate_name_index;
extern int  znx_ssl_stapling_index;

#define znx_ssl_get_conn(ssl)                           \
    SSL_get_ex_data(ssl, znx_ssl_conn_index)

#define znx_ssl_get_server_conf                         \
    SSL_get_ex_data(ssl, znx_ssl_server_conf_index)
